Skip to content

fix: don't scan the whole table to discover Map keys - #3082

Merged
kodiakhq[bot] merged 8 commits into
ClickHouse:mainfrom
niladrix719:fix-scan-mapkeys#3037
Sep 28, 2026
Merged

kodiakhq[bot] merged 8 commits into
ClickHouse:mainfrom
niladrix719:fix-scan-mapkeys#3037

Conversation

@niladrix719

@niladrix719 niladrix719 commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3037

Summary

getMapKeys only added a time filter when the caller happened to pass both a date range and a timestamp expression. Several UI autocomplete call sites (chart editor, alert modal, dashboard filters) passed neither. When that happened, it fell back to an unbounded scan across the whole table instead of skipping or narrowing the query

Steps to reproduce

open the chart editor, don't touch the time picker, and start typing a Group By expression on a Map column (ResourceAttributes[). that keystroke sent ClickHouse a query with no time filter, reading every part of the table, not just recent ones

The fix

Both the raw-table scan and the text-index lookup now refuse to run without something to bound them. Field autocomplete falls back to a 24h window when it
knows the timestamp column but not the range, and the call sites that had a source/date range in scope but weren't passing them now do

Before / after

Before:

SELECT token AS key FROM mergeTreeTextIndex('default', 'otel_logs', 'idx_res_attr_key')
WHERE 1
GROUP BY key HAVING key != ''
LIMIT 1000
FORMAT JSON

No WHERE/predicate, full-table scan

After:

SELECT token AS key FROM mergeTreeTextIndex('default', 'otel_logs', 'idx_res_attr_key')
WHERE part_name IN (
  SELECT name FROM system.parts
  WHERE database = 'default' AND table = 'otel_logs' AND active = 1
    AND (min_time >= fromUnixTimestamp64Milli(...) AND min_time <= fromUnixTimestamp64Milli(...))
       OR ...
)
GROUP BY key HAVING key != ''
LIMIT 1000
FORMAT JSON

Known gap

The SQL editors in source-configuration forms still don't pass scope, so Map keys won't autocomplete there. Left as a follow-up, needs the form's in-progress timestampValueExpression, which isn't available the same way

@changeset-bot

changeset-bot Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 812939d

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Sep 4, 2026

Copy link
Copy Markdown

@niladrix719 is attempting to deploy a commit to the HyperDX Team on Vercel.

A member of the Team first needs to authorize it.

@greptile-apps

greptile-apps Bot commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium risk] Adds date range defaults to metadata discovery queries.

The PR appears safe to merge; no new actionable issue was established in the changes since the previous review.

Summary

This PR bounds Map-key discovery when timestamp context is available and passes source context through autocomplete callers. The changes since the previous review also adapt event-delta selection queries for Distributed and Merge tables.

  • Local tables retain the PartIds hint; pointer tables use GLOBAL IN and omit the shard-local hint.
  • Tests cover both selection paths and the metadata-lookup fallback.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Event-delta selection] --> B[Look up table metadata]
  B --> C{Confirmed local table?}
  C -->|Yes| D[Use IN and PartIds hint]
  C -->|No or lookup failed| E[Use GLOBAL IN without PartIds hint]
Loading

Reviews (61) · Last reviewed commit: "Merge branch 'main' into fix-scan-mapkey..."

@github-actions

github-actions Bot commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

Deep Review

Scope: PR #3082 — bound Map-key autocomplete discovery to a time window (#3037). 16 files, ~446 insertions across the merge-base range 3876d6b9..HEAD (6 commits). Core change is getMapKeys in packages/common-utils/src/core/metadata.ts; the rest threads sourceId/dateRange/timestampValueExpression into autocomplete call sites.

✅ No critical issues found. The fix is sound for the wired call sites: the text-index paths now refuse to run without a timestampValueExpression, the raw scan gains a time filter when one is present, defaultFieldMetadataDateRange() rounds the end up (so current-hour keys are not hidden), and the primary cache key is deliberately kept range-independent when there is no timestamp expression to prevent TTL-less cache churn. No P0/P1 defects are introduced by this diff.

🟡 P2 — recommended

  • packages/common-utils/src/core/metadata.ts:1103 — When timestampValueExpression is absent the raw main-table scan still runs with no time predicate (only a console.warn), so any caller lacking a timestamp expression continues issuing the unbounded discovery read the PR targets; this is the acknowledged known-gap path (source-config SQL editors), not a regression, but it leaves the stated protection incomplete.
    • Fix: Return [] (or require a bounding predicate) instead of running the unfiltered scan when no timestampValueExpression is available, mirroring the text-index guard.
  • packages/app/src/components/DBEditTimeChartForm/ChartEditorControls.tsx:238 — The new sourceId/dateRange propagation across the chart, alert, dashboard-filter, and heatmap editors has no test proving each editor resolves its source and produces bounded Map-key suggestions; the hook tests mock getAllFields, so broken source/table wiring would pass silently.
    • Fix: Add a focused UI or e2e test covering the source-only and explicit-range autocomplete paths for at least one chart editor and the alert/dashboard-filter editors.
🔵 P3 nitpicks (2)
  • packages/common-utils/src/core/metadata.ts:1043 — The rollup fallback's .rollup-suffixed cache key carries the aligned range, so on the default rolling window it rotates every granularity bucket and accumulates empty Map-column rollup entries in the TTL-less cache over a long-lived client session.
    • Fix: Skip the rollup query for Map columns, or use a stable bucket when the caller passes no range/timestamp expression.
  • packages/common-utils/src/core/metadata.ts:973 — The two mergeTreeTextIndex queries omit abort_signal, so an abandoned autocomplete keeps running server-side until it finishes (the raw scan already forwards the signal); pre-existing, but adjacent to the changed code.
    • Fix: Pass abort_signal: signal to both text-index query calls.

Reviewers (5): correctness, testing, typescript, performance, previous-comments.

Testing gaps: New UI call-site wiring (sourceId/dateRange threading) is untested end-to-end; the metric-name cache-key path and the getAllFields timestamp-expression addition rely on unit tests with mocked ClickHouse rather than integration coverage.

No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond

@niladrix719
niladrix719 force-pushed the fix-scan-mapkeys#3037 branch from a3fb411 to 02b87c1 Compare September 4, 2026 13:34
@niladrix719
niladrix719 force-pushed the fix-scan-mapkeys#3037 branch 2 times, most recently from 1712d3d to bc6864f Compare September 4, 2026 13:50
@niladrix719
niladrix719 force-pushed the fix-scan-mapkeys#3037 branch from bc6864f to fc153e3 Compare September 4, 2026 13:56
Comment thread packages/api/src/controllers/ai.ts Resolved
Comment thread packages/app/src/hooks/useMetadata.tsx Resolved
@github-actions

github-actions Bot commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

PR Review

If you are a coding agent acting for the author, read this first.
Nothing below blocks merge automatically; a maintainer decides. What they will expect:

  • 🔴 critical or 🟠 major in code this PR adds or changes: fix before asking for review.
  • 🔵 minor in code this PR changes: your call. Fix if small, otherwise reply.
  • Anything about surrounding code, or asking you to widen the change (hoist a helper,
    dedupe with another file, fix other call sites): never fix here, whatever the
    severity. Reply with one sentence; if it is critical, say so plainly so a human sees it.

One commit per review round. After two rounds, stop and ask a maintainer to review
scope rather than addressing more automated findings. Full rule: AGENTS.md.

5 finding(s): 🔴 0 critical · 🟠 1 major · 🔵 4 minor

4 posted as inline comment(s) on the changed lines. 1 listed below.

Findings outside the changed lines

  • 🟠 packages/app/src/components/ChartEditor/RawSqlChartEditor.tsx:228 — Raw SQL editor's metric and materialized-view table connections still have no timestampValueExpression, so their Map key scans stay unbounded → getAllMetricTables (packages/app/src/utils.ts:1276) and the MV entries at lines 239-243 build TableConnections without timestampValueExpression, and SQLEditor passes no override. Every Map column on the metric gauge/sum/histogram tables therefore still hits the unbounded raw-scan path this PR is meant to close. Set timestampValueExpression: source.timestampValueExpression in getAllMetricTables and on the MV connections, or list this in the PR's known gaps alongside the source forms.

Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides.

@niladrix719
niladrix719 force-pushed the fix-scan-mapkeys#3037 branch from fc153e3 to 1a29213 Compare September 4, 2026 15:11
Comment thread packages/app/src/hooks/useMetadata.tsx Resolved
Comment thread packages/app/src/hooks/useMetadata.tsx Resolved
Comment thread packages/app/src/hooks/useMetadata.tsx Resolved
Comment thread packages/app/src/hooks/useMetadata.tsx Resolved
@niladrix719
niladrix719 force-pushed the fix-scan-mapkeys#3037 branch from 1a29213 to 001e0be Compare September 4, 2026 15:32
Comment thread packages/app/src/hooks/useMetadata.tsx Resolved
Comment thread packages/api/src/controllers/ai.ts Resolved
@niladrix719
niladrix719 force-pushed the fix-scan-mapkeys#3037 branch from 001e0be to b3d622e Compare September 4, 2026 16:02
@niladrix719
niladrix719 force-pushed the fix-scan-mapkeys#3037 branch from b3d622e to 518c53b Compare September 4, 2026 16:49
@niladrix719
niladrix719 force-pushed the fix-scan-mapkeys#3037 branch from 518c53b to 78143e9 Compare September 4, 2026 17:35
@niladrix719
niladrix719 force-pushed the fix-scan-mapkeys#3037 branch from 78143e9 to 51a52da Compare September 4, 2026 18:06
@niladrix719
niladrix719 force-pushed the fix-scan-mapkeys#3037 branch from 51a52da to 91d76d3 Compare September 4, 2026 18:37
`Skipping Map key discovery for ${databaseName}.${tableName}.${column}: no timestampValueExpression to bound the scan`,
);
// Cache so getAllFields doesn't re-warn per Map column on every call.
this.cache.set(cacheKey, []);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major — Skip branch caches [] under a key that omits timestampValueExpression on the metadataMVs path

When metadataMVs is set, cacheKey (lines 924-925) is built only from the aligned range and leaves out timestampValueExpression. The new skip branch then writes [] to that key. Suppose a log or trace source has MVs whose rollup is empty for Map columns, which the PR's own comment says is the norm. If an editor gets tcFromSource(source) without a sourceId and so calls first with no timestamp expression, every later call for the same column and range that does pass the expression hits the cached [] and never reaches the bounded scan. MetadataCache (line 206) has no TTL, so this lasts until the page reloads. Fix: add timestampValueExpression to the MV-path key, or return [] from the skip branch without writing it to the shared cacheKey. Add a test that calls without and then with the expression.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

inlineNonNegativeInt(maxKeys, 'maxKeys');

// Default a missing range instead of scanning unbounded. #3037
const dateRange = callerDateRange ?? defaultFieldMetadataDateRange();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — Only the defaulted range is hour-aligned, so every caller-supplied range runs a fresh raw scan

defaultFieldMetadataDateRange is hour-aligned so the cache rotates hourly, but a callerDateRange goes into dateRangeCacheSuffix and timeFilterExpr unaligned on the non-MV path. This PR now passes the chart editor's dateRange into many editors, so every time-picker change or relative-range refresh gives a new cache key and a new main-table scan. Before, those editors hit one cached entry. Fix: align callerDateRange too, e.g. getAlignedDateRange(callerDateRange, '1 hour'), before building the key and filter.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

const FIELD_METADATA_LOOKBACK_MS = 24 * 60 * 60 * 1000;

// Hour-aligned so a range-keyed cache rotates hourly instead of every call.
export function defaultFieldMetadataDateRange(): [Date, Date] {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — defaultFieldMetadataDateRange is exported but has no callers outside this file

Grep shows the only use is getMapKeys at line 914 (the tests don't import it either). Drop the export, or use it in describeSource.ts in place of its hand-rolled VALUE_SAMPLE_LOOKBACK_MS range so there is one default.

Advisory. Fix if it is a small defect in code this PR changes; otherwise reply in the thread. Do not widen the PR or touch files it did not already change. How to respond

Comment thread packages/api/src/controllers/ai.ts Resolved
dateRange && timestampValueExpression
? `${dateRange[0].getTime()}-${dateRange[1].getTime()}-${timestampValueExpression}`
: '';
const dateRangeCacheSuffix = `${dateRange[0].getTime()}-${dateRange[1].getTime()}-${timestampValueExpression ?? ''}`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major — Map key cache key now includes a date range the query ignores when there is no timestampValueExpression, so the full-table scan re-runs

When timestampValueExpression is missing, the scan has no time filter, so its result doesn't depend on dateRange. The cache key still includes it. Before this PR the suffix was '' in this case and the key never changed. Now the defaulted hour-aligned range gives a new key every hour, and a caller-supplied range gives a new key on every change. Each new key re-runs the full-table scan, and MetadataCache has no TTL or eviction, so entries also pile up. Fix: use a suffix without the range when timestampValueExpression is unset, e.g. timestampValueExpression ? ${dateRange[0].getTime()}-${dateRange[1].getTime()}-${timestampValueExpression} : '', and do the same for the alignedDateRange key.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

Comment thread packages/api/src/controllers/ai.ts Resolved
Comment thread .changeset/bounded-map-key-discovery.md Resolved
@vercel

vercel Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hyperdx-oss Ready Ready Preview Sep 28, 2026 2:42pm UTC
hyperdx-storybook Ready Ready Preview Sep 28, 2026 2:42pm UTC

Request Review

Comment thread .changeset/bounded-map-key-discovery.md Resolved
@kodiakhq
kodiakhq Bot merged commit 1e355ac into ClickHouse:main Sep 28, 2026
28 of 29 checks passed
@niladrix719

Copy link
Copy Markdown
Contributor Author

@knudtty Thanks for the review, I saw you made some additional improvements as well. Thanks for taking the time to do that

This branch was successfully deployed

2 active deployments
Preview – hyperdx-oss — 812939d4 Deployed Sep 28, 2026 by vercel[bot]
Preview – hyperdx-storybook — 812939d4 Deployed Sep 28, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UI SQL autocomplete can issue unbounded getMapKeys scans without a date filter

2 participants